/*
 * AStarPathFinding Library - Copyright (c) Muhammad Ahmed - quteahmed@gmail.com
 * 
 * This software is provided 'as-is', without any express or
 * implied warranty. In no event will the authors be held
 * liable for any damages arising from the use of this software.
 * 
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute
 * it freely, subject to the following restrictions:
 * 
 * 1. The origin of this software must not be misrepresented;
 * you must not claim that you wrote the original software.
 * If you use this software in a product, an acknowledgment
 * in the product documentation would be appreciated but
 * is not required.
 * 
 * 2. Altered source versions must be plainly marked as such,
 * and must not be misrepresented as being the original software.
 * 
 * 3. This notice may not be removed or altered from any
 * source distribution.
 */
package com.astar.library;

import java.util.ArrayList;

public class NodeMap {


	public enum MovableDirections
	{
		FourDirections,
		EightDirections
	}

	public enum HeuristicType
	{
		Manhattan,
		Diagonal,
		Euclidean,
		EuclideanSquared,
		TieBreakerManhattan,
		TieBreakerDiagonal,
		TieBreakerEuclidean,
		TieBreakerEuclideanSquared
	}

	/*
	 * Attributes
	 */
	private int mWidth;
	private int mHeight;
	private int mID; //ID and Name can be used to track the instances of maps.
	private float mtieBreaker = 0.001f;
	private String mName;
	private Node[][] mNodes;
	private MovableDirections mDirections;
	private HeuristicType mHeuristic;

	/*
	 * getter setters
	 * 
	 */
	public int getWidth() { return mWidth; }
	public int getHeight() { return mHeight;}

	public int getID() { return mID; }
	public void setID(int ID) { mID = ID; }

	public float getTieBreaker() { return mtieBreaker; }
	public void setTieBreaker(float value) { mtieBreaker = value; }

	public String getName() { return mName; }
	public void setName(String name) { mName = name; }

	public Node[][] getNodes() { return mNodes; }
	public void setNodes(Node[][] nodes) { mNodes = nodes; }

	public MovableDirections getMovableDirection() { return mDirections; }
	public void setMovableDirections(MovableDirections direction) {mDirections = direction; }

	public HeuristicType getHeuristicType() { return mHeuristic; }
	public void setHeuristicType(HeuristicType type) { mHeuristic = type; }

	public Node getNode(int x, int y) { return mNodes[x][y]; }
	public void setNode(int x, int y, Node node) { mNodes[x][y] = node; }


	/*
	 * constructors
	 */
	public NodeMap(int width, int height, float initialCost)
	{
		mWidth = width;
		mHeight = height;

		mNodes = new Node[width][height];
		for (int X = 0; X < width; X++)
		{
			for (int Y = 0; Y < height; Y++)
			{
				mNodes[X][Y] = new Node(X, Y, initialCost, this);
			}
		}

		mDirections = MovableDirections.FourDirections;
		mHeuristic = HeuristicType.Manhattan;
	}


	public NodeMap(int width, int height, float[][] nodeCostMap)
	{
		mWidth = width;
		mHeight = height;

		mNodes = new Node[width][height];
		for (int X = 0; X < width; X++)
		{
			for (int Y = 0; Y < height; Y++)
			{
				mNodes[X][Y] = new Node(X, Y, nodeCostMap[X][Y], this);
			}
		}

		mDirections = MovableDirections.FourDirections;
		mHeuristic = HeuristicType.Manhattan;
	}


	public NodeMap(int width, int height, float initialCost, MovableDirections directions, HeuristicType heuristics)
	{
		mWidth = width;
		mHeight = height;

		mNodes = new Node[width][height];
		for (int X = 0; X < width; X++)
		{
			for (int Y = 0; Y < height; Y++)
			{
				mNodes[X][Y] = new Node(X, Y, initialCost, this);
			}
		}

		mDirections = directions;
		mHeuristic = heuristics;
	}


	public NodeMap(int width, int height, float[][] nodeCostMap, MovableDirections directions, HeuristicType heuristics)
	{
		mWidth = width;
		mHeight = height;

		mNodes = new Node[width][height];
		for (int X = 0; X < width; X++)
		{
			for (int Y = 0; Y < height; Y++)
			{
				mNodes[X][Y] = new Node(X, Y, nodeCostMap[X][Y], this);
			}
		}

		mDirections = directions;
		mHeuristic = heuristics;
	}

	/**
	 * Used for Cloning
	 * @param width
	 * @param height
	 * @param directions
	 * @param heuristics
	 */
	private NodeMap(int width, int height, MovableDirections directions, HeuristicType heuristics)
	{
		 mWidth = width;
		 mHeight = height;

		 mNodes = new Node[width][height];

		 mDirections = directions;
		 mHeuristic = heuristics;
	}

	/*
	 * methods
	 */
	public Node[] getSurroundingNodes(int x, int y)
	{
		ArrayList<Node> tmpNodes = new ArrayList<Node>(8);
		switch (mDirections)
		{
		case FourDirections:
			if (x - 1 >= 0)
			{
				tmpNodes.add(mNodes[x - 1][y]);
			}

			if (x + 1 < mWidth)
			{
				tmpNodes.add(mNodes[x + 1][y]);
			}

			if (y - 1 >= 0)
			{
				tmpNodes.add(mNodes[x][y - 1]);
			}

			if (y + 1 < mHeight)
			{
				tmpNodes.add(mNodes[x][y + 1]);
			}
			break;

		case EightDirections:
			for (int tmpX = -1; tmpX <= 1; tmpX++)
			{
				for (int tmpY = -1; tmpY <= 1; tmpY++)
				{
					if (!(tmpY == 0 && tmpX == 0))
					{
						if (((x + tmpX) >= 0 && (x + tmpX) < mWidth) && ((y + tmpY) >= 0 && (y + tmpY) < mHeight))
						{
							tmpNodes.add(mNodes[x + tmpX][y + tmpY]);
						}
					}
				}
			}
			break;
		}
		
		Node[] nodes = new Node[tmpNodes.size()];
		for(int i = 0; i < tmpNodes.size(); i++) {
			nodes[i] = tmpNodes.get(i);
		}
		return nodes;
	}

	public float calcHeuristic(int startX, int startY, int endX, int endY)
	{
		/*
	            Manhattan:              H = Abs(Current.X - Target.X) + Abs(Current.Y - Target.Y)
	            Diagonal:               H = Max(Abs(Current.X - Target.X), Abs(Current.Y - Target.Y))
	            Euclidean:              H = Sqrt((Current.X - Target.X)^2 + (Current.Y - Target.Y)^2)
	            EuclideanSquared:       H = (Current.X - Target.X)^2 + (Current.Y - Target.Y)^2
	            TieBreakers get multiplied by a constant supplied by the user.
		 */

		float H = -1;

		switch (mHeuristic)
		{
		case Manhattan:
			H = Math.abs(startX - endX) + Math.abs(startY - endY);
			break;

		case Diagonal:
			H = Math.max(Math.abs(startX - endX), Math.abs(startY - endY));
			break;

		case Euclidean:
			H = (float) Math.sqrt(Math.pow(startX - endX, 2) + Math.pow(startY - endY, 2));
			break;

		case EuclideanSquared:
			H = (float) (Math.pow(startX - endX, 2) + Math.pow(startY - endY, 2));
			break;

		case TieBreakerManhattan:
			H = Math.abs(startX - endX) + Math.abs(startY - endY) * mtieBreaker;
			break;

		case TieBreakerDiagonal:
			H = Math.max(Math.abs(startX - endX), Math.abs(startY - endY)) * mtieBreaker;
			break;

		case TieBreakerEuclidean:
			H = (float) (Math.sqrt(Math.pow(startX - endX, 2) + Math.pow(startY - endY, 2)) * mtieBreaker);
			break;

		case TieBreakerEuclideanSquared:
			H = (float) (Math.pow(startX - endX, 2) + Math.pow(startY - endY, 2) * mtieBreaker);
			break;
		}
		return H;
	}

	@Override
	public String toString()
	{
		String tmpStr = "NodeMap( ";
		if (mName != "")
		{
			tmpStr += mName;
		}
		else
		{
			tmpStr += "Default";
		}

		if (mID != 0)
		{
			tmpStr += ", ID: " + mID + " )";
		}
		else
		{
			tmpStr += " )";
		}

		return tmpStr + String.format(" - Size[%S, %S] - Heuristic: %S - Directions: %S", mWidth, mHeight, mHeuristic.toString(), mDirections.toString());
	}


	@Override
	public NodeMap clone()
	{
		//NodeMap(int Width, int Height, MovableDirections Directions, HeuristicType Heuristics)
		NodeMap Copy = new NodeMap(mWidth, mHeight, mDirections, mHeuristic);
		for (int X = 0; X < mWidth; X++)
		{
			for (int Y = 0; Y < mHeight; Y++)
			{
				Copy.setNode(X, Y, getNode(X, Y).clone());
			}
		}
		return Copy;
	}

}
